/**
 * CS255 project 2
 */

package mitm;

import java.net.*;
import java.io.*;
import java.security.*;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.util.*;
import java.util.regex.*;

class MITMAdminServer implements Runnable
{
    private ServerSocket m_serverSocket;
    private Socket m_socket = null;
    private HTTPSProxyEngine m_engine;
    private final String passwordHash;
    private final PublicKey  m_clientPublicKey;
    private final boolean crEnabled;

    public MITMAdminServer( String localHost, int adminPort, HTTPSProxyEngine engine, String pwdHash, 
    		String clientPublicKey, boolean challengeResponseEnabled ) throws IOException,GeneralSecurityException {
	MITMSSLSocketFactory socketFactory = new MITMSSLSocketFactory();
				
	m_serverSocket = socketFactory.createServerSocket( localHost, adminPort, 0 );
	m_engine = engine;
	passwordHash = pwdHash;
	crEnabled = challengeResponseEnabled;

    //create our public key from the base64 encoded pk from the password file
    byte[] pkBytes = javax.xml.bind.DatatypeConverter.parseBase64Binary(clientPublicKey);

    m_clientPublicKey =
            KeyFactory.getInstance("RSA").generatePublic(
                    new X509EncodedKeySpec(pkBytes));


    }

    public void run()
    {
	    System.out.println("Admin server initialized, listening on port " + m_serverSocket.getLocalPort());

        while( true )
        {
            try
            {
                if (crEnabled)
                {
            	
	            	boolean authenticated = false;
	                String[] command = null;
	
	                m_socket = m_serverSocket.accept();
	
	                byte[] buffer = new byte[40960];
	
	                BufferedInputStream in =
	                    new BufferedInputStream(m_socket.getInputStream(),
	                                buffer.length);
	
	                // Read a buffer full.
	                int bytesRead = in.read(buffer);
	
	                String line =
	                    bytesRead > 0 ?
	                    new String(buffer, 0, bytesRead) : "";
	
	                // parser input
	                String request[];
	                if (((request = line.split(" ")).length == 2)
	                	&& ((command = request[1].split(":")).length == 2)
	                	&& (request[0].equals("requestChallenge"))
	                	&& (command[0].equals("command"))){
	                	
	                	//generate a random challenge
                        byte challengeBytes[] = new byte[20];
                        SecureRandom rnd = new SecureRandom();
                        rnd.nextBytes(challengeBytes);

                        String challenge = javax.xml.bind.DatatypeConverter.printBase64Binary(challengeBytes);

                        //send the salt and the challenge for the client to compute
                        sendString("challenge:" + challenge);

                        bytesRead = in.read(buffer);

                        line = (bytesRead > 0) ? new String(buffer, 0, bytesRead) : "";

                        String response[] = line.split(":");

                        if(response.length == 2 && response[0].equals("response"))
                        {
                            //get our response from the client which is the signed challenge concatinated
                            //with the command
                            String rs = response[1];
                            byte responseBytes[] = javax.xml.bind.DatatypeConverter.parseBase64Binary(rs);

                            //combine the command with the challenge
                            byte[] commandBytes =  command[1].trim().getBytes();
                            byte[] temp = new byte[commandBytes.length + challengeBytes.length];


                            System.arraycopy(commandBytes, 0, temp, 0, commandBytes.length);
                            System.arraycopy(challengeBytes, 0, temp,  commandBytes.length, challengeBytes.length);

                            //build our signature method
                            Signature s = Signature.getInstance("SHA1withRSA");
                            s.initVerify(m_clientPublicKey);
                            s.update(temp);

                            //verify signatures match
                            authenticated = s.verify(responseBytes);

                            System.out.println("Verifying challenge: " + (authenticated ? "true" : "false"));
                            
                            if( authenticated && command != null && command.length == 2)
        	                {
        	                    String cmd = command[1].trim();
        	                    doCommand( cmd );
        	                }
        	                else
        	                {
        	                    sendString("Failed to authenticate");
        	                }
                        }
                        else
                        {
                        	sendString("Malformed challenge response.  Goodbye!");
                            m_socket.close();
                            continue;
                        }
                        
	                }
                	else
                	{
                		sendString("Malformed request.  Goodbye!");
                        m_socket.close();
                        continue;
                	}	                
                }
                else
                {
                	m_socket = m_serverSocket.accept();

            		byte[] buffer = new byte[40960];

            		Pattern userPwdPattern =
            		    Pattern.compile("password:(\\S+)\\s+command:(\\S+)\\sCN:(\\S*)\\s");
            		
            		BufferedInputStream in =
            		    new BufferedInputStream(m_socket.getInputStream(),
            					    buffer.length);

            		// Read a buffer full.
            		int bytesRead = in.read(buffer);

            		String line =
            		    bytesRead > 0 ?
            		    new String(buffer, 0, bytesRead) : "";

            		Matcher userPwdMatcher =
            		    userPwdPattern.matcher(line);

            		// parse username and pwd
            		if (userPwdMatcher.find()) {
            		    String password = userPwdMatcher.group(1);            		    

            		    boolean authenticated = BCrypt.checkpw(password, passwordHash);

            		    // if authenticated, do the command
            		    if( authenticated ) {
	            			String command = userPwdMatcher.group(2);
	            			String commonName = userPwdMatcher.group(3);
	
	            			doCommand( command );
            		    }
            		    else
            		    {
            		    	sendString("Failed to authenticate.  Goodbye!");
	                        m_socket.close();
            		    }
            		}
            		else
            		{
            			sendString("Malformed login.  Goodbye!");
            			m_socket.close();
            		}
                }
            }
            catch( InterruptedIOException e )
            {
            }
            catch( Exception e )
            {
                e.printStackTrace();
            }


        }

    }

    private void sendString(final String str) throws IOException {
	PrintWriter writer = new PrintWriter( m_socket.getOutputStream() );
	writer.println(str);
	writer.flush();
    }
    
    private void doCommand( String cmd ) throws IOException
    {

       if(cmd.equals("shutdown"))
        {
            sendString("Shutting down..");
            m_socket.close();
            System.exit(0);
        }

        if(cmd.equals("stats"))
            sendString(HTTPSProxyEngine.CustomersServed + " requests proxied.");

	    m_socket.close();
	
    }

}
